經過上一個範例的練習,也大致上的知道相較於原本Spring MVC
annotation-based,Spring WebFlux
更傾向使用更Functional的Handler
&Route
,難道這樣就夠了嗎?
很顯然這樣的練習連小專案都不算,所以接下來介紹進階一點點,開始有了CRUD,常常戲稱CRUD工程師,就是要從CRUD學起。
首先下方有一個很基本的RestController
,有GET
、POST
,有PathVariable
、RequestBody
,最上方也有RequestMapping
能夠統一路徑,這次就來把他改寫為Router
+ Handler
模式。
@RestController
@RequestMapping("/mvc/greeting")
@RequiredArgsConstructor
public class GreetingController {
private final GreetingRepository repository;
@GetMapping
public Flux<Greeting> allPeople() {
return this.repository.allGreeting();
}
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public Mono<Void> saveGreeting(@RequestBody Mono<Greeting> greetingMono) {
return this.repository.saveGreeting(greetingMono);
}
@GetMapping("/{id}")
public Mono<Greeting> getGreeting(@PathVariable int id) {
return this.repository.getGreeting(id);
}
}
第一步先將邏輯的部份抽到Handler
中,因為不能確定何時才會將資料回傳,所以回傳ServerResponse
都會加上Reactive的Mono
or Flux
,Body裡面要提供type class是因為Flux的資料不是馬上就會存在,而是隨著時間傳入,也就是第一時間是沒辦法知道裡面的資料型態,所以我們要提前先指定好傳入。
public Mono<ServerResponse> allGreeting(ServerRequest request) {
Flux<Greeting> greetingFlux = this.repository.allGreeting();
return ServerResponse.ok().contentType(APPLICATION_JSON).body(greetingFlux, Greeting.class);
}
這邊的bodyToMono
,就是之前spring mvc
中 @RequestBody
直接將Body
轉成物件,這邊一樣預設是透過jackson
。
public Mono<ServerResponse> saveGreeting(ServerRequest request) {
Mono<Greeting> greetingMono = request.bodyToMono(Greeting.class);
return ServerResponse.ok().build(this.repository.saveGreeting(greetingMono));
}
將@PathVariable
取代掉,參考上面的結果很直覺就可以寫出下面的程式碼,但是當傳入一個不存在的ID,仍回傳兩百,如果想要回傳404則須調整作法,也就是現在會有兩種ServerResponse
,一種正常回傳兩百,一種找不到回傳404,根據傳統的直覺你可就直接if
else
或是三元,但在Reactive的世界中,程式執行的當下很有可能是還沒有資料進來的,也就是永遠只回傳404,如果你停下來等待結果,則又走回了blocking的老路。
public Mono<ServerResponse> getGreeting(ServerRequest request) {
int id = Integer.parseInt(request.pathVariable("id"));
Mono<Greeting> greetingMono = this.repository.getGreeting(id);
//Mono<ServerResponse> build = ServerResponse.notFound().build();
return ServerResponse.ok().contentType(APPLICATION_JSON).body(greetingMono, Greeting.class);
}
這時候Reactor有提供switchIfEmpty
讓你可以很靈活很Functional
的判斷。
public Mono<ServerResponse> getGreeting(ServerRequest request) {
int id = Integer.parseInt(request.pathVariable("id"));
Mono<Greeting> greetingMono = this.repository.getGreeting(id);
return greetingMono
.flatMap(greeting -> ServerResponse.ok().contentType(APPLICATION_JSON).body(
BodyInserters.fromValue(greeting)))
.switchIfEmpty(ServerResponse.notFound().build());
}
最後成果如下,下一篇來介紹Router
@Component
@RequiredArgsConstructor
public class GreetingHandler {
private final GreetingRepository repository;
public Mono<ServerResponse> hello(ServerRequest request) {
return ServerResponse.ok().contentType(APPLICATION_JSON)
.body(BodyInserters.fromValue(new Greeting("Hello, Spring!")));
}
public Mono<ServerResponse> getGreeting(ServerRequest request) {
int id = Integer.parseInt(request.pathVariable("id"));
Mono<Greeting> greetingMono = this.repository.getGreeting(id);
return greetingMono
.flatMap(greeting -> ServerResponse.ok().contentType(APPLICATION_JSON).body(
BodyInserters.fromValue(greeting)))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> saveGreeting(ServerRequest request) {
Mono<Greeting> greetingMono = request.bodyToMono(Greeting.class);
return ServerResponse.ok().build(this.repository.saveGreeting(greetingMono));
}
public Mono<ServerResponse> allGreeting(ServerRequest request) {
Flux<Greeting> greetingFlux = this.repository.allGreeting();
return ServerResponse.ok().contentType(APPLICATION_JSON).body(greetingFlux, Greeting.class);
}
}
感覺得出來Spring想盡辦法降低原本使用Spring Mvc的開發者學習Spring WebFlux的門檻。